/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2018 Live Networks, Inc.  All rights reserved.
// RTP sink for T.140 text (RFC 2793)
// Implementation

#include "include/T140TextRTPSink.hh"
#include "../groupsock/include/GroupsockHelper.hh" // for "gettimeofday()"

////////// T140TextRTPSink implementation //////////

T140TextRTPSink::T140TextRTPSink(UsageEnvironment &env, Groupsock *RTPgs,
                                 unsigned char rtpPayloadFormat)
        : TextRTPSink(env, RTPgs, rtpPayloadFormat,
                      1000/*mandatory RTP timestamp frequency for this payload format*/, "T140"),
          fOurIdleFilter(NULL), fAreInIdlePeriod(True) {
}

T140TextRTPSink::~T140TextRTPSink() {
    fSource = fOurIdleFilter; // hack: in case "fSource" had gotten set to NULL before we were called
    stopPlaying(); // call this now, because we won't have our 'idle filter' when the base class destructor calls it later.

    // Close our 'idle filter' as well:
    Medium::close(fOurIdleFilter);
    fSource = NULL; // for the base class destructor, which gets called next
}

T140TextRTPSink *
T140TextRTPSink::createNew(UsageEnvironment &env, Groupsock *RTPgs,
                           unsigned char rtpPayloadFormat) {
    return new T140TextRTPSink(env, RTPgs, rtpPayloadFormat);
}

Boolean T140TextRTPSink::continuePlaying() {
    // First, check whether we have an 'idle filter' set up yet. If not, create it now, and insert it in front of our existing source:
    if (fOurIdleFilter == NULL) {
        fOurIdleFilter = new T140IdleFilter(envir(), fSource);
    } else {
        fOurIdleFilter->reassignInputSource(fSource);
    }
    fSource = fOurIdleFilter;

    // Then call the parent class's implementation:
    return MultiFramedRTPSink::continuePlaying();
}

void T140TextRTPSink::doSpecialFrameHandling(unsigned /*fragmentationOffset*/,
                                             unsigned char * /*frameStart*/,
                                             unsigned numBytesInFrame,
                                             struct timeval framePresentationTime,
                                             unsigned /*numRemainingBytes*/) {
    // Set the RTP 'M' (marker) bit if we have just ended an idle period - i.e., if we were in an idle period, but just got data:
    if (fAreInIdlePeriod && numBytesInFrame > 0) setMarkerBit();
    fAreInIdlePeriod = numBytesInFrame == 0;

    setTimestamp(framePresentationTime);
}

Boolean T140TextRTPSink::frameCanAppearAfterPacketStart(unsigned char const * /*frameStart*/,
                                                        unsigned /*numBytesInFrame*/) const {
    return False; // We don't concatenate input data; instead, send it out immediately
}


////////// T140IdleFilter implementation //////////

T140IdleFilter::T140IdleFilter(UsageEnvironment &env, FramedSource *inputSource)
        : FramedFilter(env, inputSource),
          fIdleTimerTask(NULL),
          fBufferSize(OutPacketBuffer::maxSize), fNumBufferedBytes(0) {
    fBuffer = new char[fBufferSize];
}

T140IdleFilter::~T140IdleFilter() {
    envir().taskScheduler().unscheduleDelayedTask(fIdleTimerTask);

    delete[] fBuffer;
    detachInputSource(); // so that the subsequent ~FramedFilter() doesn't delete it
}

#define IDLE_TIMEOUT_MICROSECONDS 300000 /* 300 ms */

void T140IdleFilter::doGetNextFrame() {
    // First, see if we have buffered data that we can deliver:
    if (fNumBufferedBytes > 0) {
        deliverFromBuffer();
        return;
    }

    // We don't have any buffered data, so ask our input source for data (unless we've already done so).
    // But also set a timer to expire if this doesn't arrive promptly:
    fIdleTimerTask = envir().taskScheduler().scheduleDelayedTask(IDLE_TIMEOUT_MICROSECONDS,
                                                                 handleIdleTimeout, this);
    if (fInputSource != NULL && !fInputSource->isCurrentlyAwaitingData()) {
        fInputSource->getNextFrame((unsigned char *) fBuffer, fBufferSize, afterGettingFrame, this,
                                   onSourceClosure, this);
    }
}

void T140IdleFilter::afterGettingFrame(void *clientData, unsigned frameSize,
                                       unsigned numTruncatedBytes,
                                       struct timeval presentationTime,
                                       unsigned durationInMicroseconds) {
    ((T140IdleFilter *) clientData)->afterGettingFrame(frameSize, numTruncatedBytes,
                                                       presentationTime, durationInMicroseconds);
}

void T140IdleFilter::afterGettingFrame(unsigned frameSize,
                                       unsigned numTruncatedBytes,
                                       struct timeval presentationTime,
                                       unsigned durationInMicroseconds) {
    // First, cancel any pending idle timer:
    envir().taskScheduler().unscheduleDelayedTask(fIdleTimerTask);

    // Then note the new data that we have in our buffer:
    fNumBufferedBytes = frameSize;
    fBufferedNumTruncatedBytes = numTruncatedBytes;
    fBufferedDataPresentationTime = presentationTime;
    fBufferedDataDurationInMicroseconds = durationInMicroseconds;

    // Then, attempt to deliver this data.  (If we can't deliver it now, we'll do so the next time the reader asks for data.)
    if (isCurrentlyAwaitingData()) (void) deliverFromBuffer();
}

void T140IdleFilter::doStopGettingFrames() {
    // Cancel any pending idle timer:
    envir().taskScheduler().unscheduleDelayedTask(fIdleTimerTask);

    // And call the parent's implementation of this virtual function:
    FramedFilter::doStopGettingFrames();
}

void T140IdleFilter::handleIdleTimeout(void *clientData) {
    ((T140IdleFilter *) clientData)->handleIdleTimeout();
}

void T140IdleFilter::handleIdleTimeout() {
    // No data has arrived from the upstream source within our specified 'idle period' (after data was requested from downstream).
    // Send an empty 'idle' frame to our downstream "T140TextRTPSink".  (This will cause an empty RTP packet to get sent.)
    deliverEmptyFrame();
}

void T140IdleFilter::deliverFromBuffer() {
    if (fNumBufferedBytes <= fMaxSize) { // common case
        fNumTruncatedBytes = fBufferedNumTruncatedBytes;
        fFrameSize = fNumBufferedBytes;
    } else {
        fNumTruncatedBytes = fBufferedNumTruncatedBytes + fNumBufferedBytes - fMaxSize;
        fFrameSize = fMaxSize;
    }

    memmove(fTo, fBuffer, fFrameSize);
    fPresentationTime = fBufferedDataPresentationTime;
    fDurationInMicroseconds = fBufferedDataDurationInMicroseconds;

    fNumBufferedBytes = 0; // reset buffer

    FramedSource::afterGetting(this); // complete delivery
}

void T140IdleFilter::deliverEmptyFrame() {
    fFrameSize = fNumTruncatedBytes = 0;
    gettimeofday(&fPresentationTime, NULL);
    FramedSource::afterGetting(this); // complete delivery
}

void T140IdleFilter::onSourceClosure(void *clientData) {
    ((T140IdleFilter *) clientData)->onSourceClosure();
}

void T140IdleFilter::onSourceClosure() {
    envir().taskScheduler().unscheduleDelayedTask(fIdleTimerTask);
    fIdleTimerTask = NULL;

    handleClosure();
}
